'''Multiplication of several arguments.

Demo project for funtest test framework.
'''

import operator

def prod(*args):
    '''Multiplication of two or more arguments
    
    There are two ways to use this function.
    1. Call it with two or more arguments, e.g.:
    
        x = prod(1, 2, 3, 4, 5) # in effect, x should take
                                # a value of (5!).
    
    In this form prod function return a multiplication
    of all arguments given.
    
    2. Call it with one argument of iterable type, e.g.:
    
        x = prod([1, 2, 3, 4, 5]) # x still takes a value
                                  # of (5!).
    
    In this form prod function return a multiplication
    of argument items.
    
    Note, that call prod with  no arguments or with 
    one not iterable argument raises TypeError exception.
    So:
    
        x = prod(3) # meet TypeError exception
    
    but:
    
        x = prod([3]) # valid, return 3
    '''
    # No arguments given -- erroneous call.
    if not args:
        raise TypeError('prod expected several arguments, got 0')
    # Several arguments given -- return product of arguments.
    if len(args) > 1:
        return reduce(operator.mul, args, 1)
    # One argument given.
    arg = args[0]
    if len(arg) == 1:
        # The single argument is an iterable with single item.
        return arg[0]
    # The single argument is an iterable with several items.
    return prod(*arg)

#-------------------------------------------------------------------------
#                                Tests

if __name__ == '__main__':
    import funtest
    from funtest import *
    
    # The simplest cases - Nx0.
    add_OK_test(prod, 0, 0, 0)
    add_OK_suite(prod,
                 (0, 1, 0),
                 (0, 0, 1),
                 (0, 5, 0),
                 (0, 0, 5))
    # Simple cases - Nx1.
    add_OK_suite(prod,
                 (1, 1, 1),
                 (2, 2, 1),
                 (5, 1, 5))
    # More complicated cases - prod for several natural values.
    add_OK_suite(prod,
                 (4 * 3, 4, 3),
                 (4 * 3 * 3, 4, 3, 3),
                 (3 * 15 * 6, 3, 15, 6),
                 (2 * 15 * 8 * 7, 2, 15, 8, 7))
    # Prod for real values.
    add_OK_test(prod,
                7.62 * 3.14 * 5.45 * (-36.6),
                7.62, 3.14, 5.45, (-36.6))
    # Prod for complex values.
    add_OK_test(prod,
                (5.45 + 1j * 7.62) * (232 + 1j * 485) * (3.14 + 1j * 2.71),
                 5.45 + 1j * 7.62, 232 + 1j * 485, 3.14 + 1j * 2.71)
    # Prod for iterables.
    add_OK_suite(prod,
                 (0, (0, )),
                 (2, (2, )),
                 (0, (0, 1)),
                 (12, (4, 3)),
                 (4 * 3 * 7, (4, 3, 7)),
                 (4 * 3 * 7, [4, 3, 7]))
    # Exceptions check.
    add_E_test(prod, TypeError)
    add_E_suite(prod,
                (TypeError, ()),
                (TypeError, []),
                (TypeError, 1),
                (TypeError, 3))
    
    #---------------------------------------------------------------------
    #                  Failed tests examples (got AssertionError).
    
    # Expected number, got tuple multiplied.
    add_OK_test(prod, 1 * 2 * 3 * 2, (1, 2, 3), 2)
    # Expected exception, NO exception raised.
    add_E_test(prod, TypeError, (1, 2, 3), 2)
    #---------------------------------------------------------------------
    #            Erroneous tests examples (got some other exceptions).
    
    # Expected some result, got an exception, because two tuples cann't
    # been multiplied.
    add_OK_test(prod, (1, 2, 3) * 2, (1, 2, 3), (2, ))
    # Expected ValueError exception, got TypeError exception.
    add_E_test(prod, ValueError, (1, 2, 3), (2, ))
    
    #---------------------------------------------------------------------
    #           Perform all tests planned by add_* calls.
    do_tests()
